// Copyright 2016 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#pragma once

#include <lib/zircon-internal/thread_annotations.h>
#include <stddef.h>
#include <stdint.h>
#include <zircon/types.h>

#include <ddk/device.h>

#if __cplusplus

#include <memory>

#include <ddktl/device.h>
#include <ddktl/protocol/empty-protocol.h>
#include <fbl/mutex.h>

namespace tpm {

typedef uint8_t Locality;

// Abstraction over the hardware access mechanism.  The communication protocol
// relies on accessing certain hardware registers which have the same contents
// regardless of access mechanism.
class HardwareInterface {
 public:
  virtual ~HardwareInterface() {}

  // Return ZX_OK if the device represented by this interface is valid under
  // the interface's constraints.  This may perform IO to determine the
  // answer, and will be called before the device is made visible to the rest
  // of the system.
  virtual zx_status_t Validate() { return ZX_OK; }

  // Read/write the ACCESS register for the given locality
  virtual zx_status_t ReadAccess(Locality loc, uint8_t* access) = 0;
  virtual zx_status_t WriteAccess(Locality loc, uint8_t access) = 0;

  // Read/write the STS register for the given locality
  virtual zx_status_t ReadStatus(Locality loc, uint32_t* sts) = 0;
  virtual zx_status_t WriteStatus(Locality loc, uint32_t sts) = 0;

  // Read the DID_VID register, if present
  virtual zx_status_t ReadDidVid(uint16_t* vid, uint16_t* did) = 0;

  // Read/write from the DATA_FIFO register.  It is up to the caller to respect the
  // protocol's burstCount.
  virtual zx_status_t ReadDataFifo(Locality loc, uint8_t* buf, size_t len) = 0;
  virtual zx_status_t WriteDataFifo(Locality loc, const uint8_t* buf, size_t len) = 0;
};

class Device;
using DeviceType = ddk::Device<Device, ddk::Unbindable, ddk::Suspendable>;

class Device : public DeviceType, public ddk::EmptyProtocol<ZX_PROTOCOL_TPM> {
 public:
  Device(zx_device_t* parent, std::unique_ptr<HardwareInterface> iface);
  ~Device();

  static zx_status_t Create(void* ctx, zx_device_t* parent, std::unique_ptr<Device>* out);
  static zx_status_t CreateAndBind(void* ctx, zx_device_t* parent);
  static bool RunUnitTests(void* ctx, zx_device_t* parent, zx_handle_t channel);

  // Send the given command packet to the TPM and wait for a response.
  // |actual| is the number of bytes written into |resp|.
  zx_status_t ExecuteCmd(Locality loc, const uint8_t* cmd, size_t len, uint8_t* resp,
                         size_t max_len, size_t* actual);

  // Execute the GetRandom TPM command.
  zx_status_t GetRandom(void* buf, uint16_t count, size_t* actual);

  // DDK methods
  void DdkRelease();
  void DdkUnbind();
  zx_status_t DdkSuspend(uint32_t flags);

  zx_status_t Init();

 private:
  // Register this instance with devmgr and launch the deferred
  // initialization.
  zx_status_t Bind();

  // Deferred initialization of the device via a thread.  Once complete, this
  // marks the device as visible.
  static zx_status_t InitThread(void* device) {
    return reinterpret_cast<Device*>(device)->InitThread();
  }
  zx_status_t InitThread();

  // Send the given command packet to the TPM and wait for a response.
  // |actual| is the number of bytes written into |resp|.
  zx_status_t ExecuteCmdLocked(Locality loc, const uint8_t* cmd, size_t len, uint8_t* resp,
                               size_t max_len, size_t* actual) TA_REQ(lock_);

  // Request use of the given locality
  zx_status_t RequestLocalityLocked(Locality loc) TA_REQ(lock_);
  // Wait for access to the requested locality
  zx_status_t WaitForLocalityLocked(Locality loc) TA_REQ(lock_);
  // Release the given locality
  zx_status_t ReleaseLocalityLocked(Locality loc) TA_REQ(lock_);

  // Perform the transmit half of a command
  zx_status_t SendCmdLocked(Locality loc, const uint8_t* cmd, size_t len) TA_REQ(lock_);
  // Perform the receive half of a command.  |actual| will contain the total number of bytes in
  // the response, may be less than max_len.
  zx_status_t RecvRespLocked(Locality loc, uint8_t* resp, size_t max_len, size_t* actual)
      TA_REQ(lock_);

  // Issue a TPM_CC_SHUTDOWN with the given type
  zx_status_t ShutdownLocked(uint16_t type) TA_REQ(lock_);

  fbl::Mutex lock_;
  const std::unique_ptr<HardwareInterface> iface_;
};

enum tpm_result {
  TPM_SUCCESS = 0x0,
  TPM_BAD_PARAMETER = 0x3,
  TPM_DEACTIVATED = 0x6,
  TPM_DISABLED = 0x7,
  TPM_DISABLED_CMD = 0x8,
  TPM_FAIL = 0x9,
  TPM_BAD_ORDINAL = 0xa,
  TPM_RETRY = 0x800,
};

}  // namespace tpm

#endif  // __cplusplus
